package org.k.reloadable.spring;

import org.k.reloadable.core.ReloadObserver;
import org.k.reloadable.core.bean.PropertyModifiedEvent;
import org.k.reloadable.core.client.ZkClient;
import org.springframework.beans.BeansException;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Created by hengxianwang on 16-12-20.
 */
public class ReloadablePropertyPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements ReloadObserver{

    private Map<String, Set<BeanPropertyHolder>> beanPropertySubscriptions = new HashMap<String, Set<BeanPropertyHolder>>();
    private SimpleTypeConverter converter = new SimpleTypeConverter();

    @Override
    public boolean postProcessAfterInstantiation(final Object bean, final String beanName) throws BeansException {
        setPropertiesOnBean(bean);
        return true;
    }

    private void setPropertiesOnBean(final Object bean) {
        ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {

            @Override
            public void doWith(final Field field) throws IllegalArgumentException, IllegalAccessException {

                final Value annotation = field.getAnnotation(Value.class);
                if (null != annotation) {
                    ReflectionUtils.makeAccessible(field);
                    validateFieldNotFinal(bean, field);
                    String propertyKey = annotation.value();
                    if (isVariable(propertyKey)) {
                        subscribeBeanToPropertyChangedEvent(getFactKey(propertyKey), new BeanPropertyHolder(bean, field));
                    }
                }
            }
        });
    }

    /**
     * 判断是否是需要设置的变量
     * @param propertyKey
     * @return
     */
    private boolean isVariable(String propertyKey){
        return propertyKey != null && propertyKey.startsWith("${") && propertyKey.endsWith("}");
    }

    private String getFactKey(String propertyKey){
        return propertyKey.substring(2, propertyKey.length() - 1);
    }

    private void validateFieldNotFinal(final Object bean, final Field field) {
        if (Modifier.isFinal(field.getModifiers())) {
            throw new BeanInitializationException(String.format("Unable to set field [%s] of class [%s] as is declared final", field.getName(), bean.getClass()
                    .getCanonicalName()));
        }
    }

    private void subscribeBeanToPropertyChangedEvent(final String property, final BeanPropertyHolder fieldProperty) {
        String actualProperty = ZkClient.ROOT_NODE_NAME + "/" + property;
        if (!this.beanPropertySubscriptions.containsKey(actualProperty)) {
            this.beanPropertySubscriptions.put(actualProperty, new HashSet<BeanPropertyHolder>());
        }
        this.beanPropertySubscriptions.get(actualProperty).add(fieldProperty);
    }


    @Override
    public void update(PropertyModifiedEvent propertyModifiedEvent) {
        Set<BeanPropertyHolder> beanSet = this.beanPropertySubscriptions.get(propertyModifiedEvent.getPropertyName());
        if(beanSet != null) {
            for (final BeanPropertyHolder bean : beanSet) {
                updateField(bean, propertyModifiedEvent);
            }
        }
    }

    public void updateField(final BeanPropertyHolder holder, final PropertyModifiedEvent event) {
        final Object beanToUpdate = holder.getBean();
        final Field fieldToUpdate = holder.getField();

        final Object convertedProperty = convertPropertyForField(fieldToUpdate, event.getValue());
        try {
            fieldToUpdate.set(beanToUpdate, convertedProperty);
        } catch (final IllegalAccessException e) {
            //ignore
        }
    }

    private Object convertPropertyForField(final Field field, final Object property){
        return converter.convertIfNecessary(property, field.getType());
    }

}
